﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.IO;
using System.Data.SQLite;
using Microsoft.Win32;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Collections.Specialized;

namespace KeyboardImageViewer
{
    public static class EditOperations
    {
        public static void RateImage(int rating)
        {
            if (ImageCache.Current.Image != null)
            {
                Database.SetRating(ImageManager.Connection, rating, ImageListControl.CurrentImageId);

                ImageManager.MainForm.UpdateInfoBar();
            }
        }

        public static void MoveVisibleFiles()
        {
            ViewerDatabaseConfig config = ImageManager.Config;
            SQLiteConnection connection = ImageManager.Connection;

            System.Windows.Forms.FolderBrowserDialog folderDialog = new System.Windows.Forms.FolderBrowserDialog();
            folderDialog.Description = "Choose where to move the images.";
            if (config.SourceDirectories.BaseDirectories.Count == 1)
            {
                folderDialog.SelectedPath = config.SourceDirectories.BaseDirectories[0].Path;
            }

            if (folderDialog.ShowDialog() != System.Windows.Forms.DialogResult.OK)
            {
                return;
            }

            string targetFolder = folderDialog.SelectedPath;
            bool targetFolderIncluded = config.SourceDirectories.PathIsIncluded(targetFolder);

            if (!targetFolderIncluded && MessageBox.Show("The directory you specified is not included in your image collection. Do you still want to move the files there?", "Confirm Move", MessageBoxButton.YesNo) == MessageBoxResult.No)
            {
                return;
            }

            int numFilesMoved = 0;
            List<string> filesFailed = new List<string>();

            using (SQLiteTransaction transaction = connection.BeginTransaction())
            {
                SQLiteCommand updateImageName = new SQLiteCommand("UPDATE images SET name = ? WHERE id = ?", connection);
                SQLiteParameter updateNameParam = updateImageName.Parameters.Add("name", System.Data.DbType.String);
                SQLiteParameter updateIdParam = updateImageName.Parameters.Add("id", System.Data.DbType.Int32);

                SQLiteCommand disincludeImage = new SQLiteCommand("UPDATE images SET included = ? WHERE id = ?", connection);
                SQLiteParameter disincludeParam = disincludeImage.Parameters.Add("included", System.Data.DbType.Boolean);
                SQLiteParameter disincludeIdParam = disincludeImage.Parameters.Add("id", System.Data.DbType.Int32);
                disincludeParam.Value = false;

                SQLiteCommand getImageName = new SQLiteCommand("SELECT name FROM images WHERE id = ?", connection);
                SQLiteParameter getIdParam = getImageName.Parameters.Add("id", System.Data.DbType.Int32);

                foreach (int imageId in ImageListControl.ImageList)
                {
                    getIdParam.Value = imageId;
                    string oldPath;

                    using (SQLiteDataReader reader = getImageName.ExecuteReader())
                    {
                        reader.Read();
                        oldPath = reader.GetString("name");
                    }

                    if (Path.GetDirectoryName(oldPath).ToLowerInvariant() != targetFolder.ToLowerInvariant())
                    {
                        string targetFile = Path.Combine(targetFolder, Path.GetFileName(oldPath));

                        try
                        {
                            if (!File.Exists(targetFile))
                            {
                                File.Move(oldPath, targetFile);
                            }
                            else
                            {
                                targetFile = Path.Combine(targetFolder, Path.GetFileNameWithoutExtension(oldPath) + "_" + Utilities.GetRandomString(5) + Path.GetExtension(oldPath));

                                File.Move(oldPath, targetFile);
                            }

                            updateNameParam.Value = targetFile;
                            updateIdParam.Value = imageId;
                            updateImageName.ExecuteNonQuery();

                            if (!targetFolderIncluded)
                            {
                                disincludeIdParam.Value = imageId;
                                disincludeImage.ExecuteNonQuery();
                            }

                            numFilesMoved++;
                        }
                        catch (FileNotFoundException)
                        {
                            // Ignore, if the file is not there to move, we don't care.
                        }
                        catch (IOException)
                        {
                            filesFailed.Add(oldPath);
                        }
                    }
                }

                transaction.Commit();
            }

            StringBuilder messageBuilder = new StringBuilder();

            if (numFilesMoved > 0)
            {
                messageBuilder.Append(numFilesMoved + " file(s) moved to " + targetFolder + " successfully.");
            }
            else
            {
                messageBuilder.Append("No files moved.");
            }

            if (filesFailed.Count > 0)
            {
                messageBuilder.Append(Environment.NewLine);
                messageBuilder.Append(Environment.NewLine);
                messageBuilder.Append("The following files were locked or in use and could not be moved:");

                foreach (string failedFile in filesFailed)
                {
                    messageBuilder.Append(Environment.NewLine);
                    messageBuilder.Append(failedFile);
                }
            }

            MessageBox.Show(messageBuilder.ToString());

            if (targetFolderIncluded)
            {
                ImageManager.MainForm.UpdateInfoBar();
            }
            else
            {
                ImageListControl.RunImageQuery(0);
            }
        }

        public static void CopyVisibleFiles()
        {
            StringCollection fileDropList = new StringCollection();

            foreach (int imageId in ImageListControl.ImageList)
            {
                string imagePath = Database.GetImageName(ImageManager.Connection, imageId);

                if (File.Exists(imagePath))
                {
                    fileDropList.Add(imagePath);
                }
            }

            Clipboard.SetFileDropList(fileDropList);

            MessageBox.Show(fileDropList.Count + " images copied to clipboard. Use Paste in Windows Exporer to execute the copy.");
        }

        public static void EditCurrentImage()
        {
            if (ImageCache.Current.Image != null)
            {
                string imagePath = Database.GetImageName(ImageManager.Connection, ImageListControl.CurrentImageId);

                string extension = Path.GetExtension(imagePath).ToLowerInvariant();

                RegistryKey extensionKey = Registry.ClassesRoot.OpenSubKey(extension);
                string editCommandTemplate = null;

                // Look for a shell editor for this image.
                if (extensionKey != null)
                {
                    // fileType is "jpegfile" or similar.
                    string fileType = extensionKey.GetValue("") as string;

                    if (!string.IsNullOrEmpty(fileType))
                    {
                        RegistryKey editCommandKey = Registry.ClassesRoot.OpenSubKey(fileType + @"\shell\edit\command");
                        if (editCommandKey != null)
                        {
                            editCommandTemplate = editCommandKey.GetValue("") as string;
                        }
                    }

                    // If there is no editor for that extension, first check to see if it is an image type.
                    if (string.IsNullOrEmpty(editCommandTemplate))
                    {
                        string perceivedType = extensionKey.GetValue("PerceivedType") as string;

                        if (perceivedType == "image")
                        {
                            RegistryKey defaultEditorKey = Registry.ClassesRoot.OpenSubKey(@"SystemFileAssociations\image\shell\edit\command");

                            if (defaultEditorKey != null)
                            {
                                editCommandTemplate = defaultEditorKey.GetValue("") as string;
                            }
                        }
                    }
                }

                if (string.IsNullOrEmpty(editCommandTemplate))
                {
                    MessageBox.Show("Could not find image editor program for extension \"" + extension + "\".");
                    return;
                }

                string processStartCommand = editCommandTemplate.Replace("%1", imagePath);

                ProcessStartInfo processStartInfo = new ProcessStartInfo { UseShellExecute = false, FileName = processStartCommand };
                Process process = new Process { StartInfo = processStartInfo, EnableRaisingEvents = true };

                // Begin execution
                process.Exited += (object sender, EventArgs e) =>
                {
                    Process editProcess = sender as Process;

                    Action dispatcherMethod = new Action(() =>
                    {
                        if (ImageCache.ImageIsInCache(imagePath))
                        {
                            ImageListControl.RefreshImageCache();
                        }
                    });

                    ImageManager.MainForm.Dispatcher.Invoke(dispatcherMethod);
                };

                process.Start();
            }
        }

        public static void RenameCurrentImage(string newName)
        {
            string oldName = ImageManager.CurrentImageName;
            string newNameWithDir = Path.Combine(Path.GetDirectoryName(oldName), newName);

            try
            {
                File.Move(oldName, newNameWithDir);

                Database.SetImageName(ImageManager.Connection, ImageListControl.CurrentImageId, newNameWithDir);

                ImageManager.MainForm.UpdateInfoBar();
            }
            catch (IOException)
            {
                HandleMoveException();
            }
            catch (UnauthorizedAccessException)
            {
                HandleMoveException();
            }
        }

        public static void CleanVisibleItems(List<Regex> importRegList, bool deleteDuplicates)
        {
            var imagesToRemove = new List<int>();

            Dictionary<int, List<string>> currentTags = Database.GetMultipleTags(ImageManager.Connection, ImageListControl.ImageList);

            using (SQLiteTransaction transaction = ImageManager.Connection.BeginTransaction())
            {
                List<string> deleteFailures = new List<string>();
                List<KeyValuePair<string, string>> moveFailures = new List<KeyValuePair<string, string>>();

                foreach (int imageId in ImageListControl.ImageList)
                {
                    string fileName = Database.GetImageName(ImageManager.Connection, imageId);

                    if (File.Exists(fileName))
                    {
                        bool markedForDeletion = false;

                        // See if the image needs to be deleted
                        if (deleteDuplicates)
                        {
                            List<int> exactDuplicates = DuplicateUtils.FindExactDuplicates(ImageManager.Connection, imageId);

                            foreach (int duplicateImageId in exactDuplicates)
                            {
                                // If there's an exact duplicate not visible, we attempt to delete the item
                                if (!ImageListControl.ImageList.Contains(duplicateImageId))
                                {
                                    markedForDeletion = true;

                                    try
                                    {
                                        try
                                        {
                                            File.Delete(fileName);
                                        }
                                        catch (FileNotFoundException)
                                        {
                                            // If it's already gone we just go ahead and remove the file metadata.
                                        }

                                        imagesToRemove.Add(imageId);
                                    }
                                    catch (IOException)
                                    {
                                        deleteFailures.Add(Path.GetFileName(fileName));
                                    }
                                    catch (UnauthorizedAccessException)
                                    {
                                        deleteFailures.Add(Path.GetFileName(fileName));
                                    }
                                }
                            }
                        }

                        // If we did not mark the image for deletion, attempt to rename it.
                        if (!markedForDeletion)
                        {
                            string localFileName = Path.GetFileName(fileName);

                            foreach (Regex importExpression in importRegList)
                            {
                                Match m = importExpression.Match(localFileName);

                                if (m.Success)
                                {
                                    if (m.Groups["filename"].Value != "")
                                    {
                                        string newLocalName = m.Groups["filename"].Value;

                                        if (localFileName != newLocalName)
                                        {
                                            string newNameWithDir = Path.Combine(Path.GetDirectoryName(fileName), newLocalName);
                                            bool fileMoved = false;

                                            if (!File.Exists(newNameWithDir))
                                            {
                                                try
                                                {
                                                    File.Move(fileName, newNameWithDir);
                                                    fileMoved = true;

                                                    Database.SetImageName(ImageManager.Connection, imageId, newNameWithDir);
                                                }
                                                catch (IOException)
                                                {
                                                }
                                                catch (UnauthorizedAccessException)
                                                {
                                                }
                                            }

                                            if (!fileMoved)
                                            {
                                                moveFailures.Add(new KeyValuePair<string, string>(Path.GetFileName(fileName), Path.GetFileName(newNameWithDir)));
                                            }
                                        }
                                    }

                                    if (m.Groups["artist"].Value != "")
                                    {
                                        string newTag = m.Groups["artist"].Value.ToLowerInvariant();

                                        if (!currentTags[imageId].Contains(newTag))
                                        {
                                            currentTags[imageId].Add(newTag);
                                            Database.IncrementTagSummary(newTag);
                                            Database.SetTags(ImageManager.Connection, currentTags[imageId], imageId, ImageManager.Config.TagDependencies);
                                        }
                                    }

                                    break;
                                }
                            }
                        }
                    }
                }

                if (moveFailures.Count > 0 || deleteFailures.Count > 0)
                {
                    StringBuilder messageBuilder = new StringBuilder();

                    if (moveFailures.Count > 0)
                    {
                        messageBuilder.Append("Could not perform the following rename operations. Destination file already exists, or access is denied.");
                        messageBuilder.Append(Environment.NewLine);
                        messageBuilder.Append(Environment.NewLine);

                        foreach (KeyValuePair<string, string> pair in moveFailures)
                        {
                            messageBuilder.Append(pair.Key);
                            messageBuilder.Append(" -> ");
                            messageBuilder.Append(pair.Value);
                            messageBuilder.Append(Environment.NewLine);
                        }
                    }

                    if (deleteFailures.Count > 0)
                    {
                        if (moveFailures.Count > 0)
                        {
                            messageBuilder.Append(Environment.NewLine);
                        }

                        messageBuilder.Append("Could not perform the following delete operations. Files are locked or inaccessible.");
                        messageBuilder.Append(Environment.NewLine);
                        messageBuilder.Append(Environment.NewLine);

                        foreach (string deleteFile in deleteFailures)
                        {
                            messageBuilder.Append(deleteFile);
                            messageBuilder.Append(Environment.NewLine);
                        }
                    }

                    MessageBox.Show(messageBuilder.ToString(), "Import error");
                }

                transaction.Commit();
            }

            if (deleteDuplicates && imagesToRemove.Count > 0)
            {
                ImageListControl.RemoveDeletedImageData(imagesToRemove);
            }
            else
            {
                ImageListControl.RefreshImageCache();
            }
        }

        private static void HandleMoveException()
        {
            MessageBox.Show("Could not move file. Destination file already exists, or access is denied.", "Error with file move");
        }
    }
}
